package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/7/17 15:16
 * @Version 1.0
 */
@Service
public class SearchServiceImpl implements SearchService {

    //  上架：主要先给 Goods;
    //  必须有es 客户端;
    //  ElasticsearchRepository 内部有对实体类的CRUD 方法.
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RedisTemplate redisTemplate;

    //  es的高级客户端.
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    @Override
    public void upperGoods(Long skuId) {
        //  创建对象
        Goods goods = new Goods();

        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //  获取skuInfo
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            //  赋值.
            goods.setId(skuId);
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            //  可能会有脏数据.skuInfo.getPrice().doubleValue()
            goods.setPrice(productFeignClient.getSkuPrice(skuId).doubleValue());
            goods.setCreateTime(new Date());
            return skuInfo;
        },threadPoolExecutor);

        //  品牌
        CompletableFuture<Void> tmCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //  获取品牌数据
            BaseTrademark trademark = this.productFeignClient.getTrademarkById(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());

        }, threadPoolExecutor);
        //  分类数据
        CompletableFuture<Void> cateCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseCategoryView categoryView = this.productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());

        }, threadPoolExecutor);
        //  平台属性
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            List<BaseAttrInfo> attrList = this.productFeignClient.getAttrList(skuId);

            //  变量集合数据
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //  一个skuId 对应的平台属性 与 属性值只有一个！
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }, threadPoolExecutor);

        //  多任务组合
        CompletableFuture.allOf(skuInfoCompletableFuture, tmCompletableFuture,cateCompletableFuture,attrCompletableFuture).join();
        //  保存商品数据到es.
        goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  删除数据。
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHostScore(Long skuId) {
        //  1.  借助 redis 来记录商品被访问的次数
        //  考虑使用什么数据类型： String incr key/ decr key ; ZSet: zincrby key increment member;
        String hotKey = "hotScore";
        Double count = this.redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        //        this.redisTemplate.opsForValue();
        //  2。 判断这个次数与10 取模==0 就更新一次es
        if (count%10==0){
            //  更新es的hotScore;
            Optional<Goods> optional = goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            //  保存es
            goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //  1.  客户端工具类生成 dsl 语句
        SearchRequest searchRequest = this.queryBuildDsl(searchParam);
        //  2.  根据dsl 语句 查询结果
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  3.  根据查询结果封装到SearchResponseVo
        //  trademarkList attrsList goodsList total 给这四个属性赋值
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        //  给searchResponseVo 赋值操作
        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  10 3 4  9 3 3
        Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1;
        //  Long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }

    private SearchResponseVo parseResult(SearchResponse searchResponse) {
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;
         */
        //  声明一个对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //  获取hits 对象
        SearchHits hits = searchResponse.getHits();
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  获取到商品数据
        SearchHit[] subHits = hits.getHits();
        //  创建一个Goods集合
        List<Goods> goodsList = new ArrayList<>();
        //  循环遍历数组
        for (SearchHit subHit : subHits) {
            String sourceAsString = subHit.getSourceAsString();
            //  将上述字符串转换为Goods.class
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //  判断用户是否根据关键词进行了检索.
            if (subHit.getHighlightFields().get("title")!=null){
                //  说明用户是根据全文检索获取数据,应该获取高亮显示字段.
                String title = subHit.getHighlightFields().get("title").getFragments()[0].toString();
                goods.setTitle(title);
            }
            //  将单个商品添加到集合中.
            goodsList.add(goods);
        }
        //  赋值商品数据
        searchResponseVo.setGoodsList(goodsList);
        //  获取品牌数据 --- 从聚合中获取数据.
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> tmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
            //  创建品牌实体对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            String keyAsString = bucket.getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            //  获取品牌名
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);
            //  获取品牌logo
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  返回数据
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        //  赋值品牌数据
        searchResponseVo.setTrademarkList(tmVoList);
        //  平台属性聚合获取. 数据类型是nested;
        //  List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //  创建平台属性对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            String attrId = bucket.getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));

            //  获取平台属性名.
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //  获取平台属性值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");

            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(bucket1 -> bucket1.getKeyAsString()).collect(Collectors.toList());
            //  List<String> valueNameList = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueNameList);

            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值平台属性集合
        searchResponseVo.setAttrsList(attrVoList);

        //  返回对象
        return searchResponseVo;
    }

    /**
     * 生成dsl语句.
     * @param searchParam
     * @return
     */
    private SearchRequest queryBuildDsl(SearchParam searchParam) {
        //  需要构建查询器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  query --> bool
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  判断用户是否根据分类Id 进行过滤.
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //  bool--> filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //  bool--> filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //  bool--> filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        //  判断用户是否根据全文检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            //  bool --- must --- match
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //  设置高亮规则
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red>");
            highlightBuilder.postTags("</span>");
            //  设置高亮
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        //  表示查询.
        searchSourceBuilder.query(boolQueryBuilder);
        //        searchSourceBuilder.sort();
        //  判断品牌Id 过滤 1:小米
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            //  分割数据
            String[] split = trademark.split(":");
            //  判断
            if (null != split && split.length == 2){
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }
        //  平台属性值Id 过滤. 数据类型 nested.
        //  获取到前端传递的平台属性值Id
        String[] props = searchParam.getProps();
        if (null != props && props.length > 0){
            //  循环遍历
            for (String prop : props) {
                //  props=7775:黑色:颜色&props=7745:12.5寸:尺寸
                //  字符串分割
                String[] split = prop.split(":");
                //  判断
                if (null != split && split.length ==3){
                    //  创建内存的bool
                    BoolQueryBuilder innerBoolQuery = QueryBuilders.boolQuery();
                    innerBoolQuery.filter(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    innerBoolQuery.filter(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //  给外层的bool 赋值
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",innerBoolQuery, ScoreMode.None));
                }
            }
        }

        //  分页 有个分页公式：(pageNo-1)*pageSize;
        searchSourceBuilder.from((searchParam.getPageNo()-1)*searchParam.getPageSize());
        searchSourceBuilder.size(searchParam.getPageSize());

        //  排序：获取排序规则order=1:asc  order=1:desc  order=2:asc  order=2:desc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //  数据分割
            String[] split = order.split(":");
            if (null != split && split.length ==2){
                String field = "";
                //  根据条件选择
                switch (split[0]){
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                    case "3":
                        field = "createTime";
                }
                searchSourceBuilder.sort(field, "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
                //  searchSourceBuilder.sort("1".equals(split[0])?"hotScore":"price", "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }
        } else {
            //  默认排序规则
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }

        //  聚合：
        //  品牌聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //  平台属性聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                )
        );

        //  创建查询请求对象.
        SearchRequest searchRequest = new SearchRequest("goods");
        //  指定获取哪些数据
        searchSourceBuilder.fetchSource(new String[] {"id","defaultImg","title", "price"},null);
        //  打印dsl 语句
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        //  将查询器放入source中.
        searchRequest.source(searchSourceBuilder);

        return searchRequest;
    }
}
